package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.entity.vo.web.Catalog2Vo;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import sun.security.util.ArrayUtil;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> queryWithTree(Map<String, Object> params) {

        //查询所有分类数据
        List<CategoryEntity> allCategoryEntities = baseMapper.selectList(null);

        //组装成树形结构返回
        List<CategoryEntity> live1Memus = allCategoryEntities.stream().filter((categoryEntity) -> {
            //过滤出一级分类
            return categoryEntity.getParentCid() == 0;
        }).map((categoryEntity) -> {
            categoryEntity.setChildren(getChildrenCategory(categoryEntity, allCategoryEntities));
            return categoryEntity;
        }).sorted((item1, item2) -> {
            return (item1.getSort() == null ? 0 : item1.getSort()) - (item2.getSort() == null ? 0 : item2.getSort());
        }).collect(Collectors.toList());

        return live1Memus;
    }

    @Override
    public void removeMuneByIds(List<Long> asList) {
        //1.TODO 查询删除的数据是否有存在关联
        //2.做逻辑删除
        baseMapper.deleteBatchIds(asList);
    }


    @Override
    public Long[] getCatelogPath(Long catelogId) {

        List<Long> paths = new ArrayList<>();
        if (catelogId == 0) {
            return paths.toArray(new Long[0]);
        }

        //获取父分类ID信息
        List<Long> catelogPath = this.getParentPath(catelogId, paths);

        //数组旋转
        Collections.reverse(catelogPath);

        return catelogPath.toArray(new Long[catelogPath.size()]);
    }


    /**
     * @param category
     * @CacheEvict 缓存失效模式，修改数据后，清除缓存 清除模式
     * @CachePut 双写模式使用这个
     */

//    @CacheEvict(value = {"category"},key = "'getLive1Categorys'")
    //使用 @Caching 来处理多个缓存操作

    //方式一
//    @Caching(evict = {
//            @CacheEvict(value = {"category"},key = "'getLive1Categorys'"),
//            @CacheEvict(value = {"category"},key = "'getCatalogJson'")
//    })

    //方式二
    //@CacheEvict allEntries = true 删除category分区下的所有缓存
    //存储同一类型的数据最好放在同一个分区，方便统一删除
    @CacheEvict(value = "category", allEntries = true)
    @Transactional
    @Override
    public void updateRelation(CategoryEntity category) {
        //1先跟新本表数据
        this.updateById(category);
        //2跟新关联数据CategoryBrandRelationService
        //1.TODO 跟新存在关联
        if (!StringUtils.isEmpty(category.getName())) {
            categoryBrandRelationService.updateCategoryNameById(category.getCatId(), category.getName());
        }
    }

    /**
     * //默认行为
     * 1.如果缓存中有，方法不会调用
     * 2.缓存的key自动生成，格式为 缓存名字::SimpleKey[] 自动生成的key
     * 3.缓存的value的值，默认jdk序列化的值，序列化后存入redis
     * 4.默认ttl时间为-1，永不过期
     * <p>
     * 自定义
     * 1.指定生成缓存的key：   key属性来指定，接受一个SPEL表达式,如果是字符串需要用单引号
     * 2.指定缓存的失效时间：    在配置文件中配置
     * 3.将数据保存为json格式      自定义RedisCacheConfiguration
     *
     * @return
     */

    //代表当前方法的结果需要缓存，如果缓存中有，方法不会调用，直接用缓存的数据，如果没有，会被调用，最后将结果存入缓存
//    @Cacheable(value = {"category"},key = "'getLive1Categorys'")
    @Cacheable(value = {"category"}, key = "#root.method.name", sync = true)//使用方法名为key
    @Override
    public List<CategoryEntity> getLive1Categorys() {

        List<CategoryEntity> Categorys = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0L));

        return Categorys;
    }

    //优化后，从数据库读出所有数据，在内存中进行分类
    @Cacheable(value = "category", key = "'getCatalogJson'")
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {

        //缓存穿透：查询一个没有结果的值
        //解决方法：对空值对缓存，设置缓存有效期为3-5分钟

        //缓存雪崩：大量缓存同时失效
        //解决方法：在缓存时间上加个3-4分钟随机数，使每个key对缓存时间都不一样，就不会导致所有key同时失效

        //缓存击穿：对于一个高频访问的关键词数据大并发
        //解决方法：加锁，放一个进去查询数据库，其他等待，等解锁后，其他先查缓存
        String opsCategory = stringRedisTemplate.opsForValue().get("opsCategory");
        //如果缓存中没有，去数据库查询，并存入缓存中
        if (StringUtils.isEmpty(opsCategory)) {
            Map<String, List<Catalog2Vo>> levaCategoryFromDB = this.getCategoryFromDBRedisLock();

            return levaCategoryFromDB;
        }

        Map<String, List<Catalog2Vo>> result = JSON.parseObject(opsCategory, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });

        return result;
    }

    //使用redission分布式锁
    private Map<String, List<Catalog2Vo>> getCategoryFromDBRedissionLock() {

        String uuid = UUID.randomUUID().toString();
        //占锁 锁的名字，锁的粒度，越细越快
        //锁的粒度：具体缓存的是数据，如：11号商品：product-11-lock
        RLock lock = redissonClient.getLock("CategoryJson-lock");
        lock.lock();

        Map<String, List<Catalog2Vo>> dataFromDB;
        try {
            dataFromDB = getDataFromDB();
        } finally {
            lock.unlock();
        }
        return dataFromDB;

    }


    //redis分布式锁
    private Map<String, List<Catalog2Vo>> getCategoryFromDBRedisLock() {
        //1.占分布式锁，去reids占坑
//        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        //加锁和设置过期时间必须是一个原子都操作
        //如果业务代码执行的时间过长，超过了锁有效期，后面执行删锁，有可能会把别人的锁删了
        //通过uuid来解决删别人锁的问题
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {

            //枷锁成功执行业务
            //防止死锁，给锁设置过期时间,或者在try finally中,都存在一个断电问题，所有加锁和设置过期时间必须是一个原子都操作
            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<Catalog2Vo>> dataFromDB;
            try {
                dataFromDB = getDataFromDB();
            } finally {
                //使用lua脚本
                //http://www.redis.cn/commands/set.html
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
                Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            //不能直接删
//            stringRedisTemplate.delete("lock");
            //需要判断是自己uuid的锁才能删,如果getlock后返回的过程中锁过期了，自动删了，别人来上锁了，也会可能删了别人的锁
            //所以获取对比+对比成功这两步也必须是一个原子操作 使用lua脚本
//            String lockValue = stringRedisTemplate.opsForValue().get("lock");
//            if (uuid.equals(lockValue)){
//                stringRedisTemplate.delete("lock");
//            }

            return dataFromDB;
        } else {
            //枷锁失败 重试
            //防止过快，休眠100毫秒
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getCategoryFromDBRedisLock();//自旋方式
        }
    }

    private Map<String, List<Catalog2Vo>> getDataFromDB() {
        //查出所有分类
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(null);

        Map<String, List<Catalog2Vo>> stringListHashMap = null;
        //1查出所有一级分类

        List<CategoryEntity> live1Categorys = this.getLevaCategory(categoryEntities, 0L);
        if (live1Categorys != null) {
            stringListHashMap = live1Categorys.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
                //查处二级分类
                List<Catalog2Vo> catalog2Vos = null;
                List<CategoryEntity> live2Category = getLevaCategory(categoryEntities, l1.getCatId());
                if (live2Category != null) {
                    catalog2Vos = live2Category.stream().map(l2 -> {
                        //分装Catalog2Vo对象
                        Catalog2Vo catalog2Vo = new Catalog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        //查三级分类
                        List<Catalog2Vo.Catalog3Vo> l3List = null;
                        List<CategoryEntity> live3Category = getLevaCategory(categoryEntities, l2.getCatId());
                        if (live3Category != null) {
                            l3List = live3Category.stream().map(l3 -> {
                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                                return catalog3Vo;
                            }).collect(Collectors.toList());
                        }

                        catalog2Vo.setCatalog3List(l3List);
                        return catalog2Vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
        }

        //转成json字符串存redis,这一步需要放在锁内
        String s = JSON.toJSONString(stringListHashMap);
        stringRedisTemplate.opsForValue().set("opsCategory", s, 1, TimeUnit.DAYS);//缓存1天过期

        return stringListHashMap;
    }

    //本地锁
    private Map<String, List<Catalog2Vo>> getCategoryFromDBLocalLock() {

        //1.使用synchronized (this)，springboot所有的组件在容器中都是单例的，可以锁住
        //TODU 本地锁 synchronized JUC(Lock)，都是针对当前进程的，在分布式情况下，想要锁住所有，必须使用分布式锁
        synchronized (this) {
            //得到锁以后，应该再去缓存中确定一次，如果没有数据再继续查询
            String opsCategory = stringRedisTemplate.opsForValue().get("opsCategory");
            //如果缓存中没有，去数据库查询，并存入缓存中
            if (!StringUtils.isEmpty(opsCategory)) {
                //缓存不为空直接返回
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(opsCategory, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                });

                return result;
            }
            //查出所有分类
            return getDataFromDB();
        }
    }

    private List<CategoryEntity> getLevaCategory(List<CategoryEntity> categoryEntities, Long catId) {
        return categoryEntities.stream().filter(item -> item.getParentCid() == catId).collect(Collectors.toList());
    }

    //优化前
//    @Override
//    public Map<String, List<Catalog2Vo>> getCatalogJson() {
//
//        Map<String, List<Catalog2Vo>> stringListHashMap = null;
//        //1查出所有一级分类
//
//        List<CategoryEntity> live1Categorys = this.getLive1Categorys();
//        if (live1Categorys != null){
//            stringListHashMap = live1Categorys.stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
//                //查处二级分类
//                List<Catalog2Vo> catalog2Vos = null;
//                List<CategoryEntity> live2Category = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId()));
//                if (live2Category != null) {
//                    catalog2Vos = live2Category.stream().map(l2 -> {
//                        //分装Catalog2Vo对象
//                        Catalog2Vo catalog2Vo = new Catalog2Vo(l1.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//                        //查三级分类
//                        List<Catalog2Vo.Catalog3Vo> l3List = null;
//                        List<CategoryEntity> live3Category = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
//                        if (live3Category != null) {
//                            l3List = live3Category.stream().map(l3 -> {
//                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
//
//                                return catalog3Vo;
//                            }).collect(Collectors.toList());
//                        }
//
//                        catalog2Vo.setCatalog3List(l3List);
//                        return catalog2Vo;
//                    }).collect(Collectors.toList());
//                }
//                return catalog2Vos;
//            }));
//        }
//        return stringListHashMap;
//    }

    //获取父Id的路径
    private List<Long> getParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);

        CategoryEntity category = this.getById(catelogId);
        Long parentCid = category.getParentCid();

        if (parentCid != 0) {//如果父ID有效继续获取他的父分类
            this.getParentPath(parentCid, paths);
        }

        return paths;
    }

    //获取子分类
    private List<CategoryEntity> getChildrenCategory(CategoryEntity root, List<CategoryEntity> all) {

        List<CategoryEntity> liveMemus = all.stream().filter((categoryEntity) -> {
            //获取对应的子分类
            return categoryEntity.getParentCid() == root.getCatId();
        }).map((categoryEntity) -> {
            categoryEntity.setChildren(getChildrenCategory(categoryEntity, all));
            return categoryEntity;
        }).sorted((item1, item2) -> {
            return (item1.getSort() == null ? 0 : item1.getSort()) - (item2.getSort() == null ? 0 : item2.getSort());
        }).collect(Collectors.toList());

        return liveMemus;
    }

}